<!DOCTYPE html>
<html lang="zh-cn">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>在DOS下针对AC&#39;97编程 - whowin - 发表我个人原创作品的技术博客</title>
  <meta name="renderer" content="webkit" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>

<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />

<meta name="theme-color" content="#f8f5ec" />
<meta name="msapplication-navbutton-color" content="#f8f5ec">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="#f8f5ec">


<meta name="author" content="whowin" /><meta name="description" content="AC&#39;97大多数应该听说过，可能有些人把它当成一种声卡，或者是声卡上的芯片等等，其实它仅仅是一种规范，符合AC&#39;97规范的声卡，通常叫做AC&#39;97声卡，但其实上面使用的芯片可能完全不一样。现在很多桥片中甚至已经集成了AC&#39;97的规范进去，就不需要专门的声卡了。本文针对AMD的较新的一种桥片CS5536上集成的AC&#39;97进行编程，进而说明如何对符合AC&#39;97规范的声卡进行编程。以下为书写方便，把AC&#39;97写成AC97。本文是我2008年的作品，2023年重新整理发布，仅为存档，其中的程序并没有再次验证，特此说明。
" /><meta name="keywords" content="linux, socket, hugo, dos" />






<meta name="generator" content="Hugo 0.97.3 with theme even" />


<link rel="canonical" href="https://whowin.gitee.io/post/blog/dos/0010-ac97-programing-in-dos/" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">



<link href="/sass/main.min.e3fea119b1980e848b03dffbeddb11dd0fba483eed0e5f11870fb8e31f145bbd.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.1.20/dist/jquery.fancybox.min.css" integrity="sha256-7TyXnr2YU040zfSP+rEcz29ggW4j56/ujTPwjMzyqFY=" crossorigin="anonymous">


<meta property="og:title" content="在DOS下针对AC&#39;97编程" />
<meta property="og:description" content="AC&#39;97大多数应该听说过，可能有些人把它当成一种声卡，或者是声卡上的芯片等等，其实它仅仅是一种规范，符合AC&#39;97规范的声卡，通常叫做AC&#39;97声卡，但其实上面使用的芯片可能完全不一样。现在很多桥片中甚至已经集成了AC&#39;97的规范进去，就不需要专门的声卡了。本文针对AMD的较新的一种桥片CS5536上集成的AC&#39;97进行编程，进而说明如何对符合AC&#39;97规范的声卡进行编程。以下为书写方便，把AC&#39;97写成AC97。本文是我2008年的作品，2023年重新整理发布，仅为存档，其中的程序并没有再次验证，特此说明。" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://whowin.gitee.io/post/blog/dos/0010-ac97-programing-in-dos/" /><meta property="article:section" content="post" />
<meta property="article:published_time" content="2008-04-22T14:52:57+08:00" />
<meta property="article:modified_time" content="2008-04-22T14:52:57+08:00" />

<meta itemprop="name" content="在DOS下针对AC&#39;97编程">
<meta itemprop="description" content="AC&#39;97大多数应该听说过，可能有些人把它当成一种声卡，或者是声卡上的芯片等等，其实它仅仅是一种规范，符合AC&#39;97规范的声卡，通常叫做AC&#39;97声卡，但其实上面使用的芯片可能完全不一样。现在很多桥片中甚至已经集成了AC&#39;97的规范进去，就不需要专门的声卡了。本文针对AMD的较新的一种桥片CS5536上集成的AC&#39;97进行编程，进而说明如何对符合AC&#39;97规范的声卡进行编程。以下为书写方便，把AC&#39;97写成AC97。本文是我2008年的作品，2023年重新整理发布，仅为存档，其中的程序并没有再次验证，特此说明。"><meta itemprop="datePublished" content="2008-04-22T14:52:57+08:00" />
<meta itemprop="dateModified" content="2008-04-22T14:52:57+08:00" />
<meta itemprop="wordCount" content="8566">
<meta itemprop="keywords" content="DOS,AC97," /><meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="在DOS下针对AC&#39;97编程"/>
<meta name="twitter:description" content="AC&#39;97大多数应该听说过，可能有些人把它当成一种声卡，或者是声卡上的芯片等等，其实它仅仅是一种规范，符合AC&#39;97规范的声卡，通常叫做AC&#39;97声卡，但其实上面使用的芯片可能完全不一样。现在很多桥片中甚至已经集成了AC&#39;97的规范进去，就不需要专门的声卡了。本文针对AMD的较新的一种桥片CS5536上集成的AC&#39;97进行编程，进而说明如何对符合AC&#39;97规范的声卡进行编程。以下为书写方便，把AC&#39;97写成AC97。本文是我2008年的作品，2023年重新整理发布，仅为存档，其中的程序并没有再次验证，特此说明。"/>

<!--[if lte IE 9]>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/classlist/1.1.20170427/classList.min.js"></script>
<![endif]-->

<!--[if lt IE 9]>
  <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->

  <script async src="/js/busuanzi.pure.mini.js"></script><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-9724909319263152"
     crossorigin="anonymous"></script>


</head>
<body>
  <div id="mobile-navbar" class="mobile-navbar">
  <div class="mobile-header-logo">
    <a href="/" class="logo">WhoWin</a>
  </div>
  <div class="mobile-navbar-icon">
    <span></span>
    <span></span>
    <span></span>
  </div>
</div>
<nav id="mobile-menu" class="mobile-menu slideout-menu">
  <ul class="mobile-menu-list">
    <a href="/">
        <li class="mobile-menu-item">首页</li>
      </a><a href="/post/">
        <li class="mobile-menu-item">文章归档</li>
      </a><a href="/article-categories/categories/">
        <li class="mobile-menu-item">文章分类</li>
      </a><a href="/tags/">
        <li class="mobile-menu-item">文章标签</li>
      </a><a href="/about/about/">
        <li class="mobile-menu-item">关于</li>
      </a>
  </ul>

  


</nav>

  <div class="container" id="mobile-panel">
    <header id="header" class="header">
        <div class="logo-wrapper">
  <a href="/" class="logo">WhoWin</a>
  
  <div style="position:absolute; left: 80px; top: 75px; color: crimson">
      ———开源和分享是技术发展的源泉和动力；本博客所有文章均为原创
  </div>
</div>





<nav class="site-navbar">
  <ul id="menu" class="menu">
    <li class="menu-item">
        <a class="menu-item-link" href="/">首页</a>
      </li><li class="menu-item">
        <a class="menu-item-link" href="/post/">文章归档</a>
      </li><li class="menu-item">
        <a class="menu-item-link" href="/article-categories/categories/">文章分类</a>
      </li><li class="menu-item">
        <a class="menu-item-link" href="/tags/">文章标签</a>
      </li><li class="menu-item">
        <a class="menu-item-link" href="/about/about/">关于</a>
      </li>
  </ul>
</nav>

    </header>

    <main id="main" class="main">
      <div class="content-wrapper">
        <div id="content" class="content">
          <article class="post">
    
    <header class="post-header">
      <h1 class="post-title">在DOS下针对AC&#39;97编程</h1>

      <div class="post-meta">
        <span class="post-time"> 2008-04-22 </span>
        <div class="post-category">
            <a href="/categories/dos/"> DOS </a>
            </div>
        
      </div>
    </header>

    <div class="post-toc" id="post-toc">
  <h2 class="post-toc-title">文章目录</h2>
  <div class="post-toc-content always-active">
    <nav id="TableOfContents">
  <ul>
    <li>
      <ul>
        <li><a href="#1ac97规范介绍">1、AC97规范介绍</a></li>
        <li><a href="#2有关amd-cs5536有关ac97部分的介绍">2、有关AMD CS5536有关AC97部分的介绍</a></li>
        <li><a href="#3wav文件格式">3、wav文件格式</a></li>
        <li><a href="#4实例">4、实例</a></li>
      </ul>
    </li>
  </ul>
</nav>
  </div>
</div>
    <div class="post-content">
      <p>AC'97大多数应该听说过，可能有些人把它当成一种声卡，或者是声卡上的芯片等等，其实它仅仅是一种规范，符合AC'97规范的声卡，通常叫做AC'97声卡，但其实上面使用的芯片可能完全不一样。现在很多桥片中甚至已经集成了AC'97的规范进去，就不需要专门的声卡了。本文针对AMD的较新的一种桥片CS5536上集成的AC'97进行编程，进而说明如何对符合AC'97规范的声卡进行编程。以下为书写方便，把AC'97写成AC97。<strong>本文是我2008年的作品，2023年重新整理发布，仅为存档，其中的程序并没有再次验证，特此说明。</strong></p>
<h2 id="1ac97规范介绍">1、AC97规范介绍</h2>
<ul>
<li>
<p>AC97最重要的三个规范就是：</p>
<ol>
<li>使用独立的 CODEC 芯片，将数字电路和模拟电路分离；</li>
<li>固定 48K 的采样率，其他频率的信号必须经过SRC转换处理；</li>
<li>标准化的 CODEC 引脚定义。</li>
</ol>
</li>
<li>
<p>基本上说，符合这个规范的声卡就是AC97声卡。制定AC97规范的主要目的有两个：1.实现数模电路分离，保证音频质量；2.使声卡电路标准化、提高其兼容性能；</p>
</li>
<li>
<p>就AC97规范而言是十分复杂的，基本上不可能在这里表达完整，所以我们并不打算在这里完整地解释该规范，我们仅就我们准备举的例子中所需要的内容来介绍；</p>
</li>
<li>
<p>我们的程序范例准备完成一个简单的WAV文件的放音，而且，这个WAV文件还不能是很长的一个WAV文件。</p>
</li>
<li>
<p>按照AC97的规范，AC97声卡大致有三个部分：Audio Codec Controller(ACC)、AC Link、Codec，如下图：</p>
<p><img src="https://whowin.gitee.io/images/160010/three-parts-of-sound-card.jpg" alt="在DOS下针对AC97编程"></p>
</li>
<li>
<p>ACC负责与系统相连，然后透过AC Link与Codec通讯，在本例中，ACC通过AMD的GLIU与PCI总线相连，其实不用管什么GLIU，只要知道ACC与PCI总线相接就可以了；</p>
</li>
<li>
<p>ACC负责在系统存储器和Codec之间传送数据(使用DMA)，本例的ACC与AC Link之间有8个通道，相应地为了支持这8个通道，ACC有8个DMA引擎。</p>
</li>
<li>
<p>AC Link是一个5针的数字串行接口，AC97规范定义了这个接口的协议(AC Link Serial Interface Protocol)，我们必须简单地介绍这个协议才有可能完成后面的编程；</p>
</li>
<li>
<p>先要介绍一下PCM和时分多路复用的概念。</p>
<ul>
<li>PCM（Pulse Code Modulation），中文叫做脉冲编码调制，是一种编码方式，简单的说就是把声音的模拟信号变成数字信号的一种编码方式，我们使用的CD就是采用的PCM编码。AC97规范传送的声音编码都是PCM编码。</li>
<li>所谓&quot;多路复用&quot;就是为了充分利用线路资源，在一条线路上传输几路而不是一路信息；</li>
<li>&ldquo;时分&quot;指的是一种多路复用的方法，大致方法是，把一个传输通道进行时间分割以传送若干话路的信息，比如我们把1秒钟分成10份，则(0 - 1/10)秒传第1路数据，(1/10 - 2/10)秒传送第2路数据，&hellip;&hellip;，9/10&ndash;10/10秒传送第10路数据，大致就是这么个原理；</li>
<li>我们把单位时间内分成的每个等分叫做1个时隙(Time Slot)，简称Slot，比如上面把1秒分成10份，可以说有10个时隙(Slot)，Slot 0 - Slot 9。</li>
</ul>
</li>
<li>
<p>回到正题，前面说过，AC Link有5条线，分别是：SYNC、BIT_CLK、RESET#、SDATA_OUT和SDATA_IN，可以看出单方向传送的数据线，只有一条，所以一定要使用多路复用技术，AC Link协议将48KHz(20.8us)分成了13个SLOT，除第Slot 0传送16 bit数据外，其余12个Slot均传送20 bit数据，在此把我们可能用到的信息格式介绍一下。</p>
</li>
<li>
<p>Slot 0：TAG</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Bit 15       1表示该帧数据有效
</span></span><span class="line"><span class="cl">Bit 14       1表示后面Slot 1中数据有效
</span></span><span class="line"><span class="cl">Bit 13       1表示后面Slot 2中数据有效
</span></span><span class="line"><span class="cl">Bit [12:3]   1表示后面Slot 3--12中数据有效
</span></span><span class="line"><span class="cl">Bit 2        备用
</span></span><span class="line"><span class="cl">Bit [1:0]    Codec ID field
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>Slot 1：Command Address</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">bit 19       读或写标志，0--写标志，1--读标志
</span></span><span class="line"><span class="cl">bit [18:12]  64个16位寄存器索引号
</span></span><span class="line"><span class="cl">bit [11:0]   备用
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>Slot 2：Command Data</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">bit [19:4]   控制寄存器的写入数据(操作为读时，要用0填满)
</span></span><span class="line"><span class="cl">bit [3:0]    备用（填0）
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>Slot 3：PCM放音左声道</p>
<ul>
<li>高16位有效，未用到的低位填0</li>
</ul>
</li>
<li>
<p>Slot 4：PCM放音右声道</p>
<ul>
<li>高16位有效，未用到的低位填0</li>
</ul>
</li>
<li>
<p>Slot 5：MODEM Line 1 DAC</p>
<ul>
<li>高16位有效，未用到的低位填0</li>
</ul>
</li>
<li>
<p>Slot 6：PCM放音中央通道</p>
<ul>
<li>高16位有效，未用到的低位填0</li>
</ul>
</li>
<li>
<p>Slot 7：PCM放音环绕左声道</p>
<ul>
<li>高16位有效，未用到的低位填0</li>
</ul>
</li>
<li>
<p>Slot 8：PCM放音环绕右声道</p>
<blockquote>
<p>高16位有效，未用到的低位填0</p>
</blockquote>
</li>
<li>
<p>Slot 9：PCM放音低音效果声道</p>
<ul>
<li>高16位有效，未用到的低位填0</li>
</ul>
</li>
<li>
<p>Slot 10：未用</p>
</li>
<li>
<p>Slot 11：Modem Headset DAC</p>
</li>
<li>
<p>Slot 12：GPIO 控制</p>
</li>
<li>
<p>另外，规范还详细定义了CODEC的27个寄存器，AC97声卡，如果作为PCI设备，则这些寄存器可以被映射成I/O地址，也可以被映射成存储器地址，从PCI的配置空间中可以得到基地址，然后加上AC97规范中定义的寄存器的偏移便可寻址到所有的CODEC寄存器，由于内容实在是比较多，让我一个一个汉字敲上去实在是太累，所以有关协议的介绍就暂告一段落，建议大家在完成下面的例子之前还是看看AC97规范的有关章节，可以从下面地址下载。</p>
<ul>
<li><a href="https://whowin.gitee.io/specification/ac97_r23.pdf">AC97 Component Specification Revision 2.3 Rev 1.0</a></li>
</ul>
</li>
</ul>
<h2 id="2有关amd-cs5536有关ac97部分的介绍">2、有关AMD CS5536有关AC97部分的介绍</h2>
<ul>
<li>
<p>CS5536是一颗功能十分强大的芯片，我们仅能就其中我们要用到的功能做一点简要的介绍；</p>
</li>
<li>
<p>如果希望详细了解CS5536这颗芯片，可以在这里下载datasheet，篇幅很长，如果不使用这颗芯片，完全没有必要全部阅读。</p>
<ul>
<li><a href="https://whowin.gitee.io/specification/33238f_cs5536_ds.pdf">CS5536 Data Book</a></li>
</ul>
</li>
<li>
<p>前面说到，CS5536中的Codec控制器有8个通道，控制器负责从内存中向CODEC传输数据，所以在启动DMA之前，我们必须告诉控制器数据存放的内存地址，数据的长度，以及如何终止传输等等，还要告诉控制器启动哪一个或哪几个通道，CS5536的AC97控制器(ACC)为完成这些控制，有一系列的控制寄存器，偏移地址从00h&ndash;7Fh，数据均为32位；</p>
</li>
<li>
<p>首先说如何告诉ACC数据的存储地址以及与其相关的事情，CS5536上的ACC使用一个叫做PRD(Physical Region Descriptor)表作为传输控制信息的表述，表的结构如下：</p>
<p><img src="https://whowin.gitee.io/images/160010/prd-format.jpg" alt="在DOS下针对AC97编程"></p>
</li>
<li>
<p>每一个PRD表占8个字节(2个DWORD)，前1个DWORD表示数据存储的基地址，后一个DWORD的bit 0&ndash;bit 15表示数据的长度，只16bit能表示的最大值为65535，由于音频采样值为16bit，作为单声道音频流，数据必须与2个字节对齐，所以一个内存区域内存储的最大采样值(数据)为65534字节；对于双声道立体声，一个采样点位两个声道，需要4个字节，所以一个内存区域的最大采样数据为65532字节；</p>
</li>
<li>
<p>PRD表一般都不止一个，它们在内存中依次放置，就是说，如果第1个PRD表放在0x1000这个内存地址上，则第2个PRD表应该放在0x1008这个位置，第3个PDR放在0x1010，&hellip;&hellip;，以此类推，直到出现最后一个PRD表；</p>
</li>
<li>
<p>最后一个PRD表的标志是EOT位置1或者JMP位置1，不能EOT和JMP同时置1，EOT(End of Transfer)置1表明这是最后一个PRD，这个PRD后停止数据传输；</p>
</li>
<li>
<p>JMP(JUMP)置1表明跳跃到其他PRD，这时PRD的第1个DWORD不是指存放数据的内存地址，而是指JMP要跳跃到的下一个PRD表的内存地址，这种情况下表明Size的16个bit无效；</p>
</li>
<li>
<p>有效地利用JMP位，可以方便地制造出循环播放的效果，我们可以在所有PRD的最后放置一个PRD，该PRD置JMP位，同时把跳跃地址指向第1个PRD，于是循环播放的效果就出来了。</p>
</li>
<li>
<p>EOP(End of Page)是在还有下一个PRD时使用，当该页数据传输完毕后，ACC遇到EOP位，就会产生一个中断，同时转到下一个PRD，如果在下一个EOP出现之前，中断没有处理完毕的话，将出现一个错误，通常情况下会利用这个中断来填写另一个PRD表，以此来完成较长音频的播放，否则遇到很长的音频文件，岂不是要耗尽内存；</p>
</li>
<li>
<p>但我们在本例中为了突出重点，不准备播放很长的音频文件，同时，不采用中断方式。</p>
</li>
<li>
<p>好了，我们已经了解了PRD表，很显然，我们只要把第一个PRD表的地址告诉ACC就可以了，这要用到ACC的一个寄存器，偏移位置为：24h，ACC有8个这样的寄存器，偏移分别是：24h、2Ch、34h、3Ch、44h、4Ch、54h、5Ch，分别用于ACC的8个通道(是否还记得ACC有8个通道)，在本例中，我们只用通道0，所以我们只用偏移位24h的寄存器，这个寄存器的名字叫：Audio Bus Master 7-0 PRD Table Address Registers(ACC_BM<input checked="" disabled="" type="checkbox"> _PRD)，该寄存器32位长，其中bit 0和bit 1备用，bit 2&ndash;bit 31存放第1个PRD表的地址；</p>
</li>
<li>
<p>为什么bit 0和bit 1备用呢？因为规定PRD的地址必须以4字节对齐，所以实际上bit 0和bit 1没有意义。</p>
</li>
<li>
<p>如何和Codec进行通讯呢？这里还有两个ACC的寄存器必须要介绍，一个偏移量为08h的Codec状态寄存器(Codec Status Register)(ACC_CODEC_STATUS)，另一个是偏移为0Ch的Codec控制寄存器(Codec Control Register)(ACC_CODEC_CNTL)。</p>
</li>
<li>
<p>Codec控制寄存器：主要用于向Codec发出控制命令</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">bit 31(RW_CMD)：0--写Codec寄存器，1--读Codec寄存器
</span></span><span class="line"><span class="cl">bit 30:24(CMD_ADD)：读/写Codec寄存器的地址，前面说过Codec寄存器地址为7位
</span></span><span class="line"><span class="cl">bit 23:22(COMM_SEL)：与那个Codec进行通讯，00--Codec 1，01--Codec 2
</span></span><span class="line"><span class="cl">bit 16(CMD_NEW)：当填写完命令后，将此位置1，当命令发出后，硬件将此位置0
</span></span><span class="line"><span class="cl">bit 15:0(CMD_DATA)：只有写入命令时有效，欲写入Codec寄存器的数据。
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>Codec状态寄存器：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">bit 31:24(STS_ADD)：表明STS_DATA数据是哪个寄存器的
</span></span><span class="line"><span class="cl">bit 23(PRM_RDY_STS)：1--主Codec准备好，如果此位不为1，软件不能存取相应的Codec
</span></span><span class="line"><span class="cl">bit 22(SEC_RDY_STS)：1--第2个Codec准备好，如果此位不为1，软件不能存取相应的Codec
</span></span><span class="line"><span class="cl">bit 17(STS_NEW)：当收到一个合法的Codec状态数据后，硬件会将此位置1，
</span></span><span class="line"><span class="cl">bit 15:0(STS_DATA)：从Codec收到的Codec的状态数据
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>在我们的例子中，只用主Codec和通道0。</p>
</li>
<li>
<p>在本例中，Codec芯片使用的是ALC202，有关ALC202的datasheet可以在下面网址下载。</p>
<ul>
<li><a href="https://whowin.gitee.io/specification/alc202.pdf">ALC202 Audio Codec</a></li>
</ul>
</li>
</ul>
<h2 id="3wav文件格式">3、wav文件格式</h2>
<ul>
<li>
<p>由于我们的例子是打开一个WAV文件并放音，所以我们有必要了解一下WAV文件的文件格式；</p>
</li>
<li>
<p>WAVE文件作为多媒体中使用的声波文件格式之一，它是以RIFF格式为标准的；</p>
</li>
<li>
<p>RIFF是英文Resource Interchange File Format的缩写，每个WAVE文件的头四个字节便是&quot;RIFF&rdquo;；</p>
</li>
<li>
<p>WAVE文件由文件头和数据体两大部分组成；其中文件头又分为RIFF/WAV文件标识段和声音数据格式说明段两部分；WAVE文件各部分内容及格式见附表。</p>
</li>
<li>
<p>常见的声音文件主要有两种，分别对应于单声道(11.025KHz采样率、8Bit的采样值)和双声道(44.1KHz采样率、16Bit的采样值)；</p>
</li>
<li>
<p>采样率是指：声音信号在&quot;模-&gt;数&quot;转换过程中单位时间内采样的次数；采样值是指每一次采样周期内声音模拟信号的积分值。</p>
</li>
<li>
<p>对于单声道声音文件，采样数据为八位的短整数(short int 00H-FFH)；而对于双声道立体声声音文件，每次采样数据为一个16位的整数(int)，高八位和低八位分别代表左右两个声道。</p>
</li>
<li>
<p>WAVE文件数据块包含以PCM(脉冲编码调制)格式表示的样本；WAVE文件是由样本组织而成的；在双声道WAVE文件中，声道0代表左声道，声道1代表右声道；在多声道WAVE文件中，样本是交替出现的。</p>
</li>
<li>
<p>WAVE文件格式说明表</p>
<table>
<thead>
<tr>
<th style="text-align:center">偏移地址</th>
<th style="text-align:center">字节数</th>
<th style="text-align:center">数据类型</th>
<th style="text-align:left">内 容</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">00H</td>
<td style="text-align:center">4</td>
<td style="text-align:center">char</td>
<td style="text-align:left">&ldquo;RIFF&quot;标志</td>
</tr>
<tr>
<td style="text-align:center">04H</td>
<td style="text-align:center">4</td>
<td style="text-align:center">long int</td>
<td style="text-align:left">文件长度</td>
</tr>
<tr>
<td style="text-align:center">08H</td>
<td style="text-align:center">4</td>
<td style="text-align:center">char</td>
<td style="text-align:left">&ldquo;WAVE&quot;标志</td>
</tr>
<tr>
<td style="text-align:center">0CH</td>
<td style="text-align:center">4</td>
<td style="text-align:center">char</td>
<td style="text-align:left">&ldquo;fmt&quot;标志</td>
</tr>
<tr>
<td style="text-align:center">10H</td>
<td style="text-align:center">4</td>
<td style="text-align:center"></td>
<td style="text-align:left">过渡字节(不定)</td>
</tr>
<tr>
<td style="text-align:center">14H</td>
<td style="text-align:center">2</td>
<td style="text-align:center">int</td>
<td style="text-align:left">格式类别(10H为PCM形式的声音数据)</td>
</tr>
<tr>
<td style="text-align:center">16H</td>
<td style="text-align:center">2</td>
<td style="text-align:center">int</td>
<td style="text-align:left">通道数，单声道为1，双声道为2</td>
</tr>
<tr>
<td style="text-align:center">18H</td>
<td style="text-align:center">2</td>
<td style="text-align:center">int</td>
<td style="text-align:left">采样率(每秒样本数)，表示每个通道的播放速度，</td>
</tr>
<tr>
<td style="text-align:center">1CH</td>
<td style="text-align:center">4</td>
<td style="text-align:center">long int</td>
<td style="text-align:left">波形音频数据传送速率，其值为通道数×每秒数据位数×每样本的数据位数/8；播放软件利用此值可以估计缓冲区的大小</td>
</tr>
<tr>
<td style="text-align:center">20H</td>
<td style="text-align:center">2</td>
<td style="text-align:center">int</td>
<td style="text-align:left">数据块的调整数(按字节算的)，其值为通道数×每样本的数据位值/8；播放软件需要一次处理多个该值大小的字节数据，以便将其值用于缓冲区的调整</td>
</tr>
<tr>
<td style="text-align:center">22H</td>
<td style="text-align:center">2</td>
<td style="text-align:center"></td>
<td style="text-align:left">每样本的数据位数，表示每个声道中各个样本的数据位数；如果有多个声道，对每个声道而言，样本大小都一样</td>
</tr>
<tr>
<td style="text-align:center">24H</td>
<td style="text-align:center">4</td>
<td style="text-align:center">char</td>
<td style="text-align:left">数据标记符&quot;data&rdquo;</td>
</tr>
<tr>
<td style="text-align:center">28H</td>
<td style="text-align:center">4</td>
<td style="text-align:center">long int</td>
<td style="text-align:left">语音数据的长度</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>PCM数据的存放方式：</p>
<table>
<thead>
<tr>
<th style="text-align:left"></th>
<th style="text-align:left">样本1</th>
<th style="text-align:left">样本2</th>
<th style="text-align:left">样本3</th>
<th style="text-align:left">样本4</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">8位单声道</td>
<td style="text-align:left">0声道</td>
<td style="text-align:left">0声道</td>
<td style="text-align:left"></td>
<td></td>
</tr>
<tr>
<td style="text-align:left">8位立体声</td>
<td style="text-align:left">0声道(左)</td>
<td style="text-align:left">1声道(右)</td>
<td style="text-align:left">0声道(左)</td>
<td style="text-align:left">1声道(右)</td>
</tr>
<tr>
<td style="text-align:left">16位单声道</td>
<td style="text-align:left">0声道低字节</td>
<td style="text-align:left">0声道高字节</td>
<td style="text-align:left">0声道低字节</td>
<td style="text-align:left">0声道高字节</td>
</tr>
<tr>
<td style="text-align:left">16位立体声</td>
<td style="text-align:left">0声道(左)低字节</td>
<td style="text-align:left">0声道(左)高字节</td>
<td style="text-align:left">1声道(右)低字节</td>
<td style="text-align:left">1声道(右)高字节</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>WAVE文件的每个样本值包含在一个整数i中，i的长度为容纳指定样本长度所需的最小字节数。首先存储低有效字节，表示样本幅度的位放在i的高有效位上，剩下的位置为0，这样8位和16位的PCM波形样本的数据格式如下所示。</p>
<table>
<thead>
<tr>
<th style="text-align:center">样本大小</th>
<th style="text-align:center">数据格式</th>
<th style="text-align:center">最大值</th>
<th style="text-align:center">最小值</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">8位PCM</td>
<td style="text-align:center">unsigned int</td>
<td style="text-align:center">255</td>
<td style="text-align:center">0</td>
</tr>
<tr>
<td style="text-align:center">16位PCM</td>
<td style="text-align:center">int</td>
<td style="text-align:center">32767</td>
<td style="text-align:center">-32767</td>
</tr>
</tbody>
</table>
</li>
</ul>
<h2 id="4实例">4、实例</h2>
<ul>
<li>
<p>下面我们编这样一个程序，程序名：playwav.exe，带两个参数，第一个是WAV文件名，第二个是放音音量，实际使用时使用下面格式：<code>playwav filename volume</code></p>
</li>
<li>
<p>大致程序的流程如下：</p>
<ol>
<li>读入两个参数</li>
<li>搜寻AC97设备</li>
<li>初始化AC97</li>
<li>检查wav文件格式</li>
<li>读入文件并建立PRD表</li>
<li>启动AC97放音</li>
</ol>
</li>
<li>
<p>为了简化，我们要求放音文件不能很大，因为我们只打算建立三个PRD表，一次性将WAV文件中的数据全部读入内存，下面是程序清单，为了说明方便，增加了行号，程序使用C++完成，在DJGPP下编译通过。</p>
</li>
<li>
<p>由于程序太长，无法放在这里，请希望继续阅读的读者先自行下载源程序后再继续，源程序包中包括主程序playwav.cc；三个包含文件，ac97.h中定义了所有的AC97相关寄存器的偏移地址、AC97相关的常量并定义了一个类MYSOUND；typedef.h中为了程序方便定义了一些数据类型，不必过于关注；dosmem.h中主要定义了一个类DOS_MEM，主要在申请内存空间时使用，并不是本文的主题，大致明白怎么用就好了；源程序包中还有一个wav文件1.wav，用于测试。</p>
</li>
<li>
<p>源文件下载：</p>
<ol>
<li><a href="https://whowin.gitee.io/sourcecodes/160010/playwave.cc">playwave.cc</a></li>
<li><a href="https://whowin.gitee.io/sourcecodes/160010/ac97.h">ac97.h</a></li>
<li><a href="https://whowin.gitee.io/sourcecodes/160010/dosmem.h">dowmem.h</a></li>
<li><a href="https://whowin.gitee.io/sourcecodes/160010/typedef.h">typedef.h</a></li>
<li><a href="https://whowin.gitee.io/sourcecodes/160010/1.wav">1.wav</a></li>
</ol>
</li>
<li>
<p>程序的关键在ac97.h这个文件，我们将重点介绍其中的类MYSOUND。</p>
</li>
<li>
<p>现在我们假定编译出来的可执行文件是playwav.exe，我们这样来执行这个文件以便测试：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">playwav 1.wav 90
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>其中：1.wav是音频文件，90是音量。</p>
</li>
<li>
<p>我们从MYSOUND的构造函数开始；构造函数主要执行了两个内部函数：CheckPCIBios()和FindCS5536()</p>
</li>
<li>
<p>CheckPCIBios：检查BIOS是否支持PCI，这个方法我在另一篇博文<a href="https://whowin.gitee.io/post/blog/dos/0010-ac97-programing-in-dos/">《遍历PCI设备》</a>中曾经介绍过，如果BIOS不支持PCI，程序将无法运行；</p>
</li>
<li>
<p>FindCS5536：查找Codec控制器是否存在，前面介绍过，Codec控制器集成在CS5536中，ACC的VENDOR是0X1022（表示AMD公司），Device ID是0X2093，如果我们在PCI设备中能找到符合条件的设备，表明存在ACC，程序可以继续，查找PCI设备的方法在我的另一篇博文<a href="https://whowin.gitee.io/post/blog/dos/0010-ac97-programing-in-dos/">《遍历PCI设备》</a>中曾经介绍过；</p>
</li>
<li>
<p>回到构造函数，在找到ACC后，程序从配置空间中读出一些内容，其中最主要的是基地址，这个变量在后面的程序中经常用到。如何读取PCI的配置空间亦希望读者参考我以前的博文；</p>
</li>
<li>
<p>在主程序(playwav.cc)中调用的MYSOUND中的第一个方法是LoadWavFile，现在我们回到ac97.h中分析LoadWavFile这个方法；</p>
</li>
<li>
<p>LoadWavFile要求的入口参数只有一个文件名，其实就是我们执行playwav时的第一个参数1.wav，LoadWavFile方法主要调用了三个内部函数：OpenWavFile()、CheckWavFormat()和CreatePRD()</p>
</li>
<li>
<p>OpenWavFile()：仅仅是以只读、二进制的方式打开wav文件，如果成功返回handle，否则返回NULL</p>
</li>
<li>
<p>CheckWavFormat()：检查Wav文件的格式是否正确，该函数读取wav文件的文件头，并放到fileHead这个结构中，然后根据前面介绍的wav文件的格式检查其中的4个标志，如果符合则认为其是一个格式正确的wav文件；</p>
</li>
<li>
<p>CreatePRD()：这个函数很关键，这个函数将按照规则建立音频数据的缓冲区，同时建立PRD表。首先，我们建立的缓冲区中的音频数据是16bits，2通道的，就是说，每一个采样点要站4个字节，前两个字节是左声道，后两个字节是右声道，低字节在前，高字节在后；</p>
</li>
<li>
<p>一块缓冲区的长度不能超过65536，这在前面已经说过，为稳妥起见，我们决定一块缓冲区中仅存放65528个字节，也就是16382个采样点，我们首先要确定在这个缓冲区中可以从文件中读取多少个字节，对于8bit单声道数据，由于每个字节是一个采样点，所以只能读取16382个字节；对于16bit双声道数据，4个字节一个采样点，所以可以读取65528个字节；对于16bit单声道和8bit双声道数据，由于2个字节为一个采样点，所以可以读取32764个字节；可以读取的字节数存在变量m中，文件中还没有读的数据长度存在变量k中；</p>
</li>
<li>
<p>wavBuffer用于临时存储wav文件中的音频数据，wavBuf<input checked="" disabled="" type="checkbox"> 是实际按格式规范好的音频采样数据，本例中x小于8；</p>
</li>
<li>
<p>我们首先读取整块的数据到wavBuffer中，然后根据其采样宽度和声道数放到wavBuf<input checked="" disabled="" type="checkbox"> 中，注意，如果是8bit数据需要先扩展成16bit后再放入wavBuf<input checked="" disabled="" type="checkbox"> 中。</p>
</li>
<li>
<p>组织好数据后，开始设置PRD表，如果wav文件已经读完，则设置EOT位，否则设置EOP位；</p>
</li>
<li>
<p>至此，数据及PRD表均已准备完毕，可以准备AC97设备开始放音了；</p>
</li>
<li>
<p>从主程序中看到，在完成了CreatePRD()的调用后，调用了SetVolume()方法，该方法仅仅把命令行的地2个参数放到了MYSOUND类的变量volume中，在初始化AC97时将以此变量的值设置变量；</p>
</li>
<li>
<p>下面主程序调用InitialAC97()来初始化AC97；</p>
</li>
<li>
<p>InitialAC97()：该函数首先获得第一个PRD的地址，放到前面说过的位于偏移24h的ACC的PRD地址寄存器中，紧接着两行根据wav文件中的数据设置Codec的采样速率，最后两行设置了放音音量，我们把PCM_OUT的音量设为最大，然后使用主音量控制来控制音量；</p>
</li>
<li>
<p>然后主程序调用了StartPlay()方法开始放音；</p>
</li>
<li>
<p>StartPlay()：首先不断读取主Codec的状态，直到它就绪，一般情况下第一次读取就是就绪状态；然后向位于偏移20h的总线命令寄存器写入命令01h，该命令的含义是总线使能，意即开始根据PRD表向Codec传送数据，当数据传输完毕时，这个寄存器的bit 1:0将被自动置为00；</p>
</li>
<li>
<p>至此，我们已经启动了AC97的放音，前面我们说过，为了简洁地说明问题，本例不使用中断方式，而采用查询方式来完成放音过程，这主要是为了规避介绍中断例程的编写方法，我们会注意到，当主程序调用完StartPlay()方法后，便进入一个循环，不断地查询MYSOUND的状态标志status，并不停地调用方法Process()，直到status==0为止。所以我们有必要来看一下status的含义和Process()都干了些什么。</p>
</li>
<li>
<p>status：0&ndash;表示MYSOUND目前并没有放音，1&ndash;表示MYSOUND目前正在放音。构造函数里，status第一次出现，此时status=0，表明没有放音；StartPlay()里第二次出现，status=1，表示MYSOUND正在放音；Process()里第三次出现，在放音结束后status=0，说明MYSOUNG结束放音过程。</p>
</li>
<li>
<p>Process()：不停地检查总线的命令寄存器（就是当初启动传输的寄存器），前面说过党传输完成后，这个寄存器的bit 1:0将被自动置为00，该方法以此来判断传输是否完成；同时，该程序不停地检查总线IRQ状态寄存器，我们在前面也介绍过，ACC当在PRD中遇到EOP标志时，会产生中断，如果在下一个EOP来临之前不处理这个中断（读这个状态寄存器），则会产生错误，同时DMA的传输暂停，为了不造成这种现象，Process()不停地读这个状态寄存器。</p>
</li>
<li>
<p>至此，程序基本介绍完了，过程有些繁琐，由于篇幅原因，很多问题不得不请大家自己去读一些规范，让我一个字一个字地敲上去太难了，希望这篇文章能给你一些帮助。</p>
</li>
</ul>
<hr>
<p><strong>欢迎访问我的博客：https://whowin.cn</strong></p>
<p><strong>email: <a href="mailto:hengch@163.com">hengch@163.com</a></strong></p>
<p><img src="https://whowin.gitee.io/images/qrcode/sponsor-qrcode.png" alt="donation"></p>
    </div>

    <div class="post-copyright">
  <p class="copyright-item">
    <span class="item-title">文章作者</span>
    <span class="item-content">whowin</span>
  </p>
  <p class="copyright-item">
    <span class="item-title">上次更新</span>
    <span class="item-content">
        2008-04-22
        
    </span>
  </p>
  
  
</div>
<footer class="post-footer">
      <div class="post-tags">
          <a href="/tags/dos/">DOS</a>
          <a href="/tags/ac97/">AC97</a>
          </div>
      <nav class="post-nav">
        <a class="prev" href="/post/blog/dos/0011-hw-int-mechanism-under-dos-dpmi/">
            <i class="iconfont icon-left"></i>
            <span class="prev-text nav-default">DOS DPMI下的硬件中断机制</span>
            <span class="prev-text nav-mobile">上一篇</span>
          </a>
        <a class="next" href="/post/blog/dos/0008-network-programming-on-dos-2/">
            <span class="next-text nav-default">在DOS下进行网络编程（下）</span>
            <span class="next-text nav-mobile">下一篇</span>
            <i class="iconfont icon-right"></i>
          </a>
      </nav>
    </footer>
  </article>
        </div>
        

  <span id="/post/blog/dos/0010-ac97-programing-in-dos/" class="leancloud_visitors" data-flag-title="在DOS下针对AC&#39;97编程">
		<span class="post-meta-item-text">文章阅读量 </span>
		<span class="leancloud-visitors-count">0</span>
		<p></p>
	  </span>
  <div id="vcomments"></div>
  <script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
  <script src='//unpkg.com/valine/dist/Valine.min.js'></script>
  <script type="text/javascript">
    new Valine({
        el: '#vcomments' ,
        appId: 'OFCGzCfJRUglzOdzrqMGkbTR-gzGzoHsz',
        appKey: 'v7P29kPAEbsmaavaYPNhGhnF',
        notify:  false ,
        verify:  false ,
        avatar:'mm',
        placeholder: '说点什么吧...',
        visitor:  true 
    });
  </script>

  

      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="social-links">
      <a href="mailto:hengch@163.com" class="iconfont icon-email" title="email"></a>
  <a href="https://whowin.gitee.io/index.xml" type="application/rss+xml" class="iconfont icon-rss" title="rss"></a>
</div>
<div class="copyright">
  <span class="power-by">
    由 <a class="hexo-link" href="https://gohugo.io">Hugo</a> 强力驱动
  </span>
  <span class="division">|</span>
  <span class="theme-info">
    主题 - 
    <a class="theme-link" href="https://github.com/olOwOlo/hugo-theme-even">Even</a>
  </span>

  <div class="busuanzi-footer">
    <span id="busuanzi_container_site_pv"> 本站总访问量 <span id="busuanzi_value_site_pv"><img src="/img/spinner.svg" alt="spinner.svg"/></span> 次 </span>
      <span class="division">|</span>
    <span id="busuanzi_container_site_uv"> 本站总访客数 <span id="busuanzi_value_site_uv"><img src="/img/spinner.svg" alt="spinner.svg"/></span> 人 </span>
  </div>

  <span class="copyright-year">
    &copy; 
    2022 - 
    2024<span class="heart"><i class="iconfont icon-heart"></i></span><span>whowin</span>
  </span>
</div>

    </footer>

    <div class="back-to-top" id="back-to-top">
      <i class="iconfont icon-up"></i>
    </div>
  </div>
  
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/slideout@1.0.1/dist/slideout.min.js" integrity="sha256-t+zJ/g8/KXIJMjSVQdnibt4dlaDxc9zXr/9oNPeWqdg=" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.1.20/dist/jquery.fancybox.min.js" integrity="sha256-XVLffZaxoWfGUEbdzuLi7pwaUJv1cecsQJQqGLe7axY=" crossorigin="anonymous"></script>



<script type="text/javascript" src="/js/main.min.64437849d125a2d603b3e71d6de5225d641a32d17168a58106e0b61852079683.js"></script>
  <script type="text/javascript">
    window.MathJax = {
      tex: {
        inlineMath: [['$','$'], ['\\(','\\)']],
        }
    };
  </script>
  <script async src="https://cdn.jsdelivr.net/npm/mathjax@3.0.5/es5/tex-mml-chtml.js" integrity="sha256-HGLuEfFcsUJGhvB8cQ8nr0gai9EucOOaIxFw7qxmd+w=" crossorigin="anonymous"></script>








</body>
</html>
